package com.zmn.oms.third.todm.track;

import com.zmn.common.constant.GlobalConsts;
import com.zmn.oms.common.constant.TodmConsts;
import com.zmn.oms.common.utils.ExceptionUtil;
import com.zmn.oms.manager.utils.SpringBeanFactoryUtil;
import com.zmn.oms.model.entity.log.OrderChannelDockingLog;
import com.zmn.oms.zmn.business.interfaces.log.OrderChannelDockingLogBService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * 类描述：渠道对接跟踪器
 *
 * @author lhl
 * @date 2019/11/28 11:34
 *
 * 方法介绍：
 *  使用方法分为三个部分：1.开启跟踪 2.记录跟踪数据 3.提交跟踪数据并结束跟踪
 *      1.开启跟踪，开启跟踪方法为必须调用方法
 *      ChannelDockingTracker.getInstance().dataIn();  // 开启一个由第三方渠道同步到啄木鸟的跟踪
 *      ChannelDockingTracker.getInstance().dataOut(); // 开启一个由啄木鸟同步到第三方渠道的跟踪
 *
 *      2.记录跟踪数据，包含必须调用方法和非必须调用方法
 *      ChannelDockingTracker.getInstance().setChannelId();    // 设置第三方渠道的Id，必须
 *      ChannelDockingTracker.getInstance().setOuterId();      // 设置第三方订单Id，必须
 *      ChannelDockingTracker.getInstance().setOperation();    // 设置操作类型，即本次同步的目的是什么，必须
 *      ChannelDockingTracker.getInstance().setOrderId();      // 关联的订单ID，必须，同步新单失败除外（未获取到新单ID）
 *
 *      ChannelDockingTracker.getInstance().setReqSummary();   // 请求数据摘要，非必须，多次调用以最后一次为准
 *      ChannelDockingTracker.getInstance().setRespSummary();  // 响应数据摘要，非必须，多次调用以最后一次为准
 *      ChannelDockingTracker.getInstance().addRemark();       // 追加备注，用于业务人员查看订单跟踪情况，非必须，多次调用结果累加
 *      ChannelDockingTracker.getInstance().error();           // 记录异常，非必须，多次调用结果累加
 *      ChannelDockingTracker.getInstance().success();         // 标识同步是否成功，非必须
 *
 *      3.提交跟踪数据并结束跟踪
 *      Todm.tracker().commit();  // 必须，否则会丢失本次跟踪数据
 *
 * 使用说明：
 *      1. 以上方法都支持链式编程；
 *      2. 外部使用时推荐使用 Todm.tracker().xxx() 方式，不建议直接使用 ChannelDockingTracker.getInstance().xxx() 方式调用；
 * 使用示例：
 *      1. 开启跟踪
 *      Todm.tracker()
 *          .dataIn()
 *          .setChannelId(xxx)
 *          .setOperation(xxx)
 *          .setOrderId(xxx)
 *          .setReqSummary(xxx);
 *      2. 记录处理进度说明，异常等
 *      Todm.tracker()
 *          .addRemark(xxx)
 *          .error(e)
 *          .addRemark(xxx)
 *          .error(e)
 *          .setRespSummary(xxx);
 *      3. 结束跟踪并保存跟踪记录
 *      Todm.tracker()
 *          .success(true)
 *          .commit();
 */
@Slf4j
public class ChannelDockingTracker {

    /**
     * trackHolder
     */
    private static ThreadLocal<ChannelDockingTracker> trackHolder = new ThreadLocal();
    /**
     * 特定格式的时间格式化工具
     */
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    static {
        dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
    }
    /**
     * 跟踪事件对象
     */
    private OrderChannelDockingLog orderChannelDockingLog;
    /**
     * 备注缓存，用于缓存多次添加的备注
     */
    private StringBuilder remarkBuffer;
    /**
     * 导致同步程序中断的异常
     */
    private List<Exception> exceptions;


    /* ======================= 开启跟踪 ====================== */
    /**
     *  开启追踪，标识本次追踪的数据流向：第三方渠道-->系统
     * @return
     */
    public ChannelDockingTracker dataIn() {
        return open(TodmConsts.DATA_IN);
    }

    /**
     *  开启追踪，标识本次追踪的数据流向：系统-->第三方渠道
     * @return
     */
    public ChannelDockingTracker dataOut() {
        return open(TodmConsts.DATA_OUT);
    }

    /* ======================= 跟踪中 ====================== */
    /**
     * 添加第三方订单ID
     * @param outerId
     */
    public ChannelDockingTracker setOuterId(String outerId) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setOuterId(outerId);
        }
        return this;
    }
    /**
     * 添加订单ID
     * @param orderIdStr
     */
    public ChannelDockingTracker setOrderId(String orderIdStr) {
        Long orderId = null;
        try {
            orderId = Long.parseLong(orderIdStr);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        OrderChannelDockingLog log = getLog();
        if (orderId != null && log != null) {
            log.setOrderId(orderId);
        }
        return this;
    }
    /**
     * 添加订单ID
     * @param orderId
     */
    public ChannelDockingTracker setOrderId(Long orderId) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setOrderId(orderId);
        }
        return this;
    }
    /**
     * 添加渠道ID
     * @param channelId
     */
    public ChannelDockingTracker setChannelId(Integer channelId) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setChannelId(channelId);
        }
        return this;
    }

    /**
     * 添加操作类型
     * @param operation
     */
    public ChannelDockingTracker setOperation(Integer operation) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setOperation(operation);
        }
        return this;
    }

    /**
     * 设置请求数据摘要
     */
    public ChannelDockingTracker setReqSummary(String reqSummary) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setReqSummary(reqSummary);
        }
        return this;
    }

    /**
     * 设置响应数据摘要
     */
    public ChannelDockingTracker setRespSummary(String respSummary) {
        OrderChannelDockingLog log = getLog();
        if (log != null) {
            log.setRespSummary(respSummary);
        }
        return this;
    }

    /**
     * 追加备注，建议每次追加的备注内容都以分号“；”结尾
     * @param remark
     */
    public ChannelDockingTracker addRemark(String remark) {
        if (this.remarkBuffer != null) {
            this.remarkBuffer.append(remark).append("\n");
        }
        return this;
    }

    /**
     * 追加备注，建议每次追加的备注内容都以分号“；”结尾
     * @param template
     * @param remark
     */
    public ChannelDockingTracker addRemark(String template, String... remark) {
        return addRemark(String.format(template, remark));
    }

    /**
     * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
     * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
     * @param e
     * @param errorMsg
     */
    public ChannelDockingTracker error(Exception e, String errorMsg) {
        if (this.exceptions == null) {
            this.exceptions.add(e);
        }

        if (this.remarkBuffer != null && StringUtils.isNotBlank(errorMsg)) {
            this.remarkBuffer.append(errorMsg).append("\n");
        }
        return this;
    }

    /**
     * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
     * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
     * @param e
     * @param template
     * @param strings
     */
    public ChannelDockingTracker error(Exception e, String template, String... strings) {
        return error(e, String.format(template, strings));
    }

    /**
     * 记录异常信息，本方法在一次跟踪记录中可以调用多次，不会直接影响本次跟踪的订单的同步结果。
     * 本方法可以传入被吃掉处理的异常，也可以传入上抛的异常。
     * @param errorMsg
     * @param e
     */
    @Deprecated
    public ChannelDockingTracker error(String errorMsg, Exception e) {
        if (this.exceptions == null) {
            this.exceptions.add(e);
        }

        if (this.remarkBuffer != null && StringUtils.isNotBlank(errorMsg)) {
            this.remarkBuffer.append(errorMsg).append("\n");
        }
        return this;
    }

    /* ======================= 完成跟踪 ====================== */
    /**
     * 标记本次跟踪的订单同步结果
     */
    public ChannelDockingTracker success(boolean isSuccess) {
        return success(isSuccess, null);
    }
    /**
     * 标记本次跟踪的订单同步结果
     * @param isSuccess
     * @param errorMsg
     */
    public ChannelDockingTracker success(boolean isSuccess, String errorMsg) {
        OrderChannelDockingLog log = getLog();
        if (log == null) {
            return this;
        }

        if (StringUtils.isBlank(errorMsg)) {
            errorMsg = isSuccess ? "成功" : "失败";
        }
        log.setSuccess(isSuccess ? GlobalConsts.YES : GlobalConsts.NO);
        log.setErrorMsg(errorMsg);
        return this;
    }

    /**
     * 确定订单同步结果，提交当前跟踪事件，关闭本次跟踪过程。
     * 确当订单同步结果的逻辑如下：
     *  1.如果本方法调用前，调用过success()或fail()方法，则以这两个方法标识的结果作为最终同步结果，是否调用error()方法不对结果产生影响；
     *  2.如果本方法调用前，未调用过success()和fail()方法，但调用过error()方法，则设置同步结果为失败；
     *  3.如果本方法调用前，success()、fail()和error()方法均未调用过，则设置同步结果为成功；
     */
    public void commit() {
        try {
            ChannelDockingTracker track = trackHolder.get();
            if (track == null) {
                return;
            }
            OrderChannelDockingLog dockingLog = track.orderChannelDockingLog;
            if (dockingLog == null) {
                return;
            }
            if (dockingLog.getOuterId() == null) {
                // TODO -lhl 没有渠道单号，怎么处理?
            }

            {// 完善日志信息
                // 结束时间
                dockingLog.setEndTime(currentTime());
                // 备注
                StringBuilder remarkBuffer = track.remarkBuffer;
                if (remarkBuffer.length() > 0) {
                    dockingLog.setRemark(remarkBuffer.toString());
                }
                // 是否存在异常
                List<Exception> exceptions = track.exceptions;
                boolean isError = !exceptions.isEmpty();
                dockingLog.setError(isError ? GlobalConsts.YES : GlobalConsts.NO);
                // 如果同步过程中出现了异常，保存这些异常信息
                if (isError) {
                    StringBuilder errorDetailBuilder = new StringBuilder();
                    // 收集异常堆栈信息，越后添加的越重要，要优先显示
                    for (int i=exceptions.size()-1; i<0; i--) {
                        errorDetailBuilder.append(ExceptionUtil.getStackTrace(exceptions.get(i)));
                    }
                    dockingLog.setErrorStackTrace(errorDetailBuilder.toString());
                }
                // 如果没有显示的标记同步结果，即没有调用success()和fail()方法，则发生异常判为失败，否则判为成功
                if (dockingLog.getSuccess() == null) {
                    dockingLog.setSuccess(isError ? GlobalConsts.NO : GlobalConsts.YES);
                }
            }

            // 保存追踪信息
            OrderChannelDockingLogBService logBService = SpringBeanFactoryUtil.getBean("orderChannelDockingLogBService");
            if (logBService != null) {
                logBService.save(dockingLog);
            }
            else {
                log.warn("[ChannelDockingTracker] No OrderChannelDockingLogBService instance found!");
            }

            // 清空当前持有的跟踪事件
            trackHolder.remove();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 获取追踪示例
     * @return
     */
    public static ChannelDockingTracker getInstance() {
        ChannelDockingTracker tracker = trackHolder.get();
        if (tracker == null) {
            tracker = new ChannelDockingTracker();
            trackHolder.set(tracker);
        }
        return tracker;
    }

    /**
     * 丢弃追踪示例
     * @return
     */
    public static void discard() {
        trackHolder.remove();
    }

    /* *******************************************/
    /* ************* Private Method **************/
    /* *******************************************/

    /**
     * 开启跟踪
     * @param type 数据走向类型
     * 使用常量：OrderSyncConsts.TYPE_IN, OrderSyncConsts.TYPE_OUT
     * @return
     */
    private ChannelDockingTracker open(Integer type) {
        this.remarkBuffer = new StringBuilder();
        this.exceptions = new ArrayList<>();

        OrderChannelDockingLog orderSyncLog = new OrderChannelDockingLog();
        orderSyncLog.setOrderId(0L);    // 默认订单号为0，多用于添加新单失败时，没有订单号的情况
        orderSyncLog.setBeginTime(currentTime());
        orderSyncLog.setType(type);
        this.orderChannelDockingLog = orderSyncLog;

        return this;
    }

    /**
     * 获取当前跟踪的事件对象
     * @return
     */
    private OrderChannelDockingLog getLog() {
        return this.orderChannelDockingLog;
    }

    /**
     * 获取当前时间的指定格式化形式
     * @return
     */
    private static long currentTime() {
        return Long.parseLong(dateFormat.format(new Date()));
    }
}
